package emulator.history;

public class BitStateHistory
{
	private BitStateChange [] buffer;
	private int head;
	private int tail;
	
	public BitStateHistory(int bufSize)
	{
		buffer = new BitStateChange [bufSize+1];
		head = 0;
		tail = 0;
	}
	
	public synchronized void add(BitStateChange bitStateChange) throws IllegalArgumentException
	{
		if (!isEmpty())
		{
			BitStateChange lastElement = buffer[advance(head, -1)];
			if (lastElement.getTick() > bitStateChange.getTick())
				throw new IllegalArgumentException("Time warp detected");
			if (lastElement.getValue() == bitStateChange.getValue())
				throw new IllegalArgumentException("State did not change");
		}
		buffer[head] = bitStateChange;
		head = advance(head, 1);
		if (head == tail)
			tail = advance(tail, 1);
	}
	
	private int advance(int index, int count)
	{
		int newIndex = (index + count) % buffer.length;
		return newIndex < 0 ? newIndex + buffer.length : newIndex;
	}
	
	private BitStateChange at(int pos)
	{
		return buffer[advance(tail, pos)];
	}
	
	private int getCount()
	{
		return advance(head, -tail);
	}

	private boolean isEmpty()
	{
		return head == tail;
	}

	
	public synchronized BitStateChange [] getRange(long firstTick, long lastTick)
	{
		if (!isEmpty())
		{
			int rangeStart = getGreaterOrEqualIndex(firstTick);
			if (rangeStart >= 0)
			{
				int rangeSize = getLessOrEqualIndex(lastTick) - rangeStart + 1;
				if (rangeSize > 0)
				{
					BitStateChange [] result = new BitStateChange[rangeSize];
					for (int i = 0; i < rangeSize; i++)
						result[i] = at(rangeStart+i);
					return result;
				}
			}
		}
		return new BitStateChange [0];
	}

	public synchronized BitStateChange getGreaterOrEqualElement(long tick)
	{
		int index = getGreaterOrEqualIndex(tick);
		return index < 0 ? null : at(index);
	}
	
	public int getGreaterOrEqualIndex(long tick)
	{
		if (!isEmpty())
		{
			int firstIndex = 0;
			if (at(firstIndex).getTick() >= tick)
				return 0;
			int lastIndex = getCount() - 1;
			if (at(lastIndex).getTick() >= tick)
			{
				while (firstIndex < lastIndex)
				{
					int middle = (firstIndex + lastIndex) / 2;
					if (at(middle).getTick() < tick)
						firstIndex = middle+1;
					else
						lastIndex = middle;
				}
				return firstIndex;
			}
		}
		return -1;
	}

	public synchronized BitStateChange getLessOrEqualElement(long tick)
	{
		int index = getLessOrEqualIndex(tick);
		return index < 0 ? null : at(index);
	}
	
	public int getLessOrEqualIndex(long tick)
	{
		if (!isEmpty())
		{
			int lastIndex = getCount() - 1;
			if (at(lastIndex).getTick() <= tick)
				return lastIndex;
			int firstIndex = 0;
			if (at(firstIndex).getTick() <= tick)
			{
				while (firstIndex < lastIndex)
				{
					int middle = (firstIndex + lastIndex + 1) / 2;
					if (at(middle).getTick() > tick)
						lastIndex = middle-1;
					else
						firstIndex = middle;
				}
				return firstIndex;
			}
		}
		return -1;
	}
}
